package org.axed.user.client.axessory;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.ui.Widget;
import java.util.Vector;
import org.axed.user.client.AxedArea;
import org.axed.user.client.AxedEvent;
import org.axed.user.client.AxedListener;
import org.axed.user.client.DOM;
import org.axed.user.client.Line;
import org.timepedia.exporter.client.Exportable;

/**
 * The vertical position bar is placed Left to an axed widget and shows
 * the line numbers. It honors warping of lines. A newline number is only 
 * displayed after a hard carridge return.
 * 
 * @gwt.exportPackage axed
 * @gwt.export
 */
public class VPositionBar extends Widget implements AxedListener, Exportable {
	/**
	 * The bar with the line numbers on it.
	 */
	Element bar;
	
	/**
	 * The table body for the lines.
	 */
	Element tbody;

	/**
	 * The table for the lines.
	 */
	Element table;

	/**
	 * A vector having the tr elements of the line numbers.
	 */
	Vector v = new Vector();

	int width;
	int height;

	/**
 	 * the biggest number displayed, used to determine width
 	 */
	int maxline;

	/**
	 * The minimum of decimals for the width.
	 */
	int minDecimals;

	/**
	 * The widget this belongs to.
	 */
	AxedArea axed;

	/**
	 * True when sending a refresh event, hinders recursive selfreaction.
	 */
	boolean selfRefresh = false;

	/**
	 * Constructor 
	 *
	 * @param axed         the axed widget this belongs to.
	 * @param setDiv       the div to use, if null a new one will be created.
	 * @param height       the height of this position bar.
	 * @param minDecimals  minimum of decimals for this width.
	 */
	public VPositionBar(AxedArea axed, Element setDiv, int minDecimals) {
		super();
		this.axed = axed;

		this.width = 0;
		this.height = axed.setHeight - 1;
		this.minDecimals = minDecimals;

		if (setDiv == null) {
			bar = DOM.createDiv();
		} else {
			bar = setDiv;
		}
		DOM.setStyleAttribute(bar, "borderTop", "1px solid");
		DOM.setStyleAttribute(bar, "borderBottom", "1px solid");
		DOM.setStyleAttribute(bar, "height", height + "px");
		DOM.setStyleAttribute(bar, "background", "#CCCCCC");
		DOM.setStyleAttribute(bar, "margin", "0px");
		DOM.setStyleAttribute(bar, "padding", "0px");
		DOM.setStyleAttribute(bar, "overflow", "hidden");
		setElement(bar);
	}

	/**
	 * Handles events from the axed widget.
	 * @gwt.noexport
	 */
	public void onAxedEvent(AxedEvent event) {
		switch (event.type) {
		case AxedEvent.LINE_HEIGHT_CHANGED :
			resizeLine(event.getLineHeightChanged().line);
			break;
		case AxedEvent.FONT_SIZE_CHANGED :
			changedFontSize();
			break;
		case AxedEvent.SPLIT_LINE :
			newline(event.getSplitLine().pnt.line + 1);
			break;
		case AxedEvent.CUT :
		case AxedEvent.PASTE :
			buildBar();
			break;
		case AxedEvent.REFRESH :
			if (!selfRefresh) {
				buildBar();
			}
			break;
		case AxedEvent.INSERT_TEXT :
		case AxedEvent.REMOVE_TEXT :
			buildBar();
			break;
		case AxedEvent.JOIN_LINE :
			joinLine(event.getJoinLine().pnt.line);
			break;
		case AxedEvent.SCROLL :
			scroll();
			break;
		}	
	}

	/**
	 * Sets the height.
	 */
	public void setHeight(int height) {
		DOM.setStyleAttribute(bar, "height", (height - 1) + "px");
		buildBar();
	}

//privates:
	/**
 	 * rebuilds the bar.
 	 */
	private void buildBar() {
		//int spos = axed.getScrollTop();
		DOM.setInnerHTML(bar, "");
		v.clear();

		table = DOM.createTable();
		DOM.setStyleAttribute(table, "borderCollapse", "collapse");
		DOM.setStyleAttribute(table, "borderSpacing", "0px");
		DOM.setStyleAttribute(table, "borderWidth", "0px");
		DOM.setStyleAttribute(table, "fontSize", "0.8em");

		tbody = DOM.createTBody();
		DOM.appendChild(bar, table);
		DOM.appendChild(table, tbody);
		for (int i = 0; i < axed.document.getLineCount(); i++) {
			Line l = axed.document.getLine(i);
			Element tr = createLineIdentifier(l.getCurses().getHeight(), i);
			DOM.appendChild(tbody, tr);
			v.add(tr);
		}
		setWidth();
	}

	/**
	 * Called when the font size changed.
	 */
	private void changedFontSize() {
		buildBar();
	}


	/**
	 * Creates a line identifier.
	 *
	 * @return the DOM element for the number for the line.
	 */
	private Element createLineIdentifier(int height, int linenum) {
		Element tr = DOM.createTR();
		Element td = DOM.createTD();
		DOM.appendChild(tr, td);
		DOM.setStyleAttribute(td, "padding", "0px");
		DOM.setStyleAttribute(td, "spacing", "0px");
		DOM.setStyleAttribute(td, "textAlign", "right");
		Element div = DOM.createDiv();
		DOM.setStyleAttribute(div, "height", height + "px");
		DOM.setInnerHTML(div, "" + (linenum + 1));
		DOM.appendChild(td, div);
		return tr;
	}

	/**
	 * Returns the div element of line p.
	 */
	private Element getDiv(int p) {
		return DOM.getFirstChild(DOM.getFirstChild(getTr(p)));
	}

	/**
	 * Returns the row element of line p.
	 */
	private Element getTr(int p) {
		return (Element) v.get(p);
	}

	/**
	 * User pressed carridge return. 
	 * A new line is inserted, and the lines below have to be renumerated.
	 */
	private void newline(int line) {
		Line l = axed.document.getLine(line);
		Element tr = createLineIdentifier(l.getCurses().getHeight(), line - 1);
		if (line >= axed.document.getLineCount()) {
			DOM.appendChild(tbody, tr);
		} else {
			DOM.insertBefore(tbody, tr, (Element) v.get(line - 1));
		}
		v.add(line - 1, tr);
		resizeLine(line - 1);
		resizeLine(line);

		for (int i = line; i < v.size(); i++) {
			DOM.setInnerHTML(getDiv(i), "" + (i + 1));
		}
		setWidth();
	}


	/**
	 * A line has been join-removed either through del or backspace
	 */
	private void joinLine(int line) {
		DOM.removeChild(tbody, getTr(line + 1));
		v.remove(line + 1);
		resizeLine(line); 
		for (int i = line + 1; i < v.size(); i++) {
			DOM.setInnerHTML(getDiv(i), "" + (i + 1));
		}
		setWidth();
	}

	/**
	 * Resizea a line.
	 */
	private void resizeLine(int line) {
		Line l = axed.document.getLine(line);
		DOM.setStyleAttribute(getDiv(line), "height", l.getCurses().getHeight() + "px");
	}

	/**
	 * Scrolls the position bar.
	 */
	private void scroll() {
		DOM.setElementPropertyInt(bar, "scrollTop", axed.getScrollTop()); 
	}

	/**
	 * TODO
	 */
	private void setWidth() {
		int lc = v.size();
		Element tr = getTr(lc - 1);
		Element td = DOM.getFirstChild(tr);
		Element div = DOM.getFirstChild(td);
		int nw = DOM.getElementPropertyInt(div, "offsetWidth");
		int nw2 = nw;
		int dc = 1;
		while (lc >= 10) {
			dc++;	
			lc /= 10;
		}

		if (minDecimals > 0 && dc < minDecimals) {
			nw2 = (nw * minDecimals)/dc;
		}

		DOM.setStyleAttribute(bar, "width", (nw2 + 2) + "px");
		DOM.setStyleAttribute(table, "marginLeft", (nw2 - nw) + "px");
		if (nw != width) {
			selfRefresh = true;
			axed.refresh();
			selfRefresh = false;
		}
		width = nw;
	}

}
